﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;
using System.Text;

namespace Ruleweaver.Rules
{
	public abstract class RuleVisitor
	{
		protected virtual Rule Visit(Rule rule)
		{
			Contract.Requires(rule != null);
			Contract.Ensures(Contract.Result<Rule>() != null);

			switch(rule.Type)
			{
				case RuleType.Check:
					return VisitCheck((CheckRule) rule);
				case RuleType.Fixed:
					return VisitFixed((FixedRule) rule);
				case RuleType.And:
				case RuleType.Or:
				case RuleType.ExclusiveOr:
					return VisitComposite((CompositeRule) rule);
				case RuleType.Field:
				case RuleType.Property:
				case RuleType.Method:
					return VisitMember((MemberRule) rule);
				case RuleType.Not:
					return VisitNot((NotRule) rule);
				default:
					return rule;
			}
		}

		protected virtual Rule VisitCheck(CheckRule checkRule)
		{
			Contract.Requires(checkRule != null);
			Contract.Ensures(Contract.Result<Rule>() != null);

			return checkRule;
		}

		protected virtual Rule VisitFixed(FixedRule fixedRule)
		{
			Contract.Requires(fixedRule != null);
			Contract.Ensures(Contract.Result<Rule>() != null);

			return fixedRule;
		}

		protected virtual Rule VisitComposite(CompositeRule compositeRule)
		{
			Contract.Requires(compositeRule != null);
			Contract.Ensures(Contract.Result<Rule>() != null);

			var visitedRules = new List<Rule>(compositeRule.Rules.Count);

			var ruleChanged = false;

			foreach(var rule in compositeRule.Rules)
			{
				var visitedRule = Visit(rule);

				visitedRules.Add(visitedRule);

				if(visitedRule != rule)
				{
					ruleChanged = true;
				}
			}

			if(ruleChanged)
			{
				compositeRule = Rule.Composite(compositeRule.Type, visitedRules);
			}

			return compositeRule;
		}

		protected virtual Rule VisitMember(MemberRule memberRule)
		{
			Contract.Requires(memberRule != null);
			Contract.Requires(memberRule.Type == RuleType.Field || memberRule.Type == RuleType.Property || memberRule.Type == RuleType.Method);
			Contract.Ensures(Contract.Result<Rule>() != null);

			switch(memberRule.Type)
			{
				case RuleType.Field:
					return VisitField(memberRule);
				case RuleType.Property:
					return VisitProperty(memberRule);
				default:
					return VisitMethod(memberRule);
			}
		}

		protected virtual Rule VisitField(MemberRule fieldRule)
		{
			Contract.Requires(fieldRule != null);
			Contract.Requires(fieldRule.Type == RuleType.Field);
			Contract.Ensures(Contract.Result<Rule>() != null);

			var visitedRule = Visit(fieldRule.Rule);

			if(visitedRule != fieldRule.Rule)
			{
				fieldRule = Rule.Field((FieldInfo) fieldRule.Member, visitedRule);
			}

			return fieldRule;
		}

		protected virtual Rule VisitProperty(MemberRule propertyRule)
		{
			Contract.Requires(propertyRule != null);
			Contract.Requires(propertyRule.Type == RuleType.Property);
			Contract.Ensures(Contract.Result<Rule>() != null);

			var visitedRule = Visit(propertyRule.Rule);

			if(visitedRule != propertyRule.Rule)
			{
				propertyRule = Rule.Property((PropertyInfo) propertyRule.Member, visitedRule);
			}

			return propertyRule;
		}

		protected virtual Rule VisitMethod(MemberRule methodRule)
		{
			Contract.Requires(methodRule != null);
			Contract.Requires(methodRule.Type == RuleType.Method);
			Contract.Ensures(Contract.Result<Rule>() != null);

			var visitedRule = Visit(methodRule.Rule);

			if(visitedRule != methodRule.Rule)
			{
				methodRule = Rule.Method((MethodInfo) methodRule.Member, visitedRule);
			}

			return methodRule;
		}

		protected virtual Rule VisitNot(NotRule notRule)
		{
			Contract.Requires(notRule != null);
			Contract.Ensures(Contract.Result<Rule>() != null);

			var visitedRule = Visit(notRule.Rule);

			if(visitedRule != notRule.Rule)
			{
				notRule = Rule.Not(visitedRule);
			}

			return notRule;
		}
	}
}